import argparse
import numpy as np
import cv2
import struct
import re

from typing import List


def encode(frames: List[str], img_shape: tuple) -> int:
    """
    将各个帧编码成图片组
    :param frames:各个帧的字符串表示
    :param img_shape:图片的长宽
    :return:page:int 图片张数 保存图片在当前目录pic文件夹下
    """
    rows, cols = img_shape
    row, col = 15, 15  # 当前行，当前列，上下各空15个像素宽度
    data = ''
    for item in frames:
        data += item

    page = 0
    imageRead = np.ones((rows, cols, 3), np.uint8)
    imageRead *= 255
    for c in data:
        if c == '0':
            for i in range(3):
                for j in range(3):
                    imageRead[row + i, col + j] = (0, 0, 0)
        col += 3
        if col >= cols - 15:  # 换行
            col = 15
            row += 3
            if row >= rows - 15:  # 换图
                row = 15
                cv2.imwrite('pic/pic' + str(page) + '.png', imageRead)
                imageRead = np.ones((rows, cols, 3), np.uint8)
                imageRead *= 255
                page += 1
    if row != 0 or col != 0:
        cv2.imwrite('pic/pic' + str(page) + '.png', imageRead)
    return page + 1


def img2video(video_file: str, fps: float, num_of_frames: int):
    fourcc = cv2.VideoWriter_fourcc('I', '4', '2', '0')
    Video_Writer = cv2.VideoWriter(video_file, fourcc, fps, (480, 270), True)
    for i in range(num_of_frames):
        frame = cv2.imread('pic/pic' + str(i) + '.png')
        Video_Writer.write(frame)
    Video_Writer.release()


def calculate_crc(data_list: List[str]) -> list:
    """
    计算每个字符串的crc16
    :param data_list: List[str] 
    :return: crc: List[int]
    """
    strbuf = []
    crc = []
    for item in data_list:
        for start in range(0, len(item), 8):
            # 每次取8位01字符串（1字节），转化为十进制
            sub = item[start:start + 8]
            byte = BinStr2DecInt(sub, 1)
            strbuf.append(byte)
            if len(strbuf) == max_data_len:
                crc.append(crc16(strbuf, len(strbuf)))
                strbuf.clear()
    if len(strbuf) > 0:
        # 末尾不足max_data_len的，归为一组计算crc16
        crc.append(crc16(strbuf, len(strbuf)))
    return crc


def BinStr2DecInt(s: str, num_of_bytes: int) -> int:
    """
    长度为8的二进制01字符串转十进制整数
    :param s: str
    :param num_of_bytes: int 字节位数
    :return: res: int
    """
    res = 0
    s = s[::-1]
    for i in range(8 * num_of_bytes):
        if s[i] == '1':
            res += 2 ** i
    return res


def BinFile2string(bin_file: str) -> str:
    """
    二进制文件转换成01字符串（大端序）
    :param bin_file: str
    :return res: str
    """
    fileReader = open(bin_file, 'rb')
    res = ''
    num = fileReader.read(1)
    while num:
        num = struct.unpack('B', num)
        num = num[0]
        res += DecInt2BinStr(num, 1)
        num = fileReader.read(1)
    fileReader.close()
    return res


def DecInt2BinStr(x: int, num_of_bytes: int) -> str:
    """
    十进制整数转二进制字符串
    :param x: int 带转换的十进制整数
    :param num_of_bytes: int 转为几个字节位长的二进制
    :return: res: str 二进制字符串表示
    """
    res = ''
    for i in range(num_of_bytes * 8):
        res += str(x % 2)
        x //= 2
    return res[::-1]


def crc16(strbuf: list, length: int):
    """
    根据整数列表(实际为二进制的表示)计算crc16
    :param strbuf: 整数列表
    :param length: len(strbuf)
    :return: result: int
    """
    result = 0
    for m in range(length):
        result = result & 0xFFFF  # 因为Python的int整形数没有最大值，所以需要&上0xffff
        tempcrc16 = ((result >> 8) ^ strbuf[m]) & 0xffff
        tempdata = (tempcrc16 << 8)
        tempcrc16 = 0

        for n in range(8):
            if (tempdata ^ tempcrc16) & 0x8000:
                tempcrc16 = (tempcrc16 << 1) ^ 0x1021
            else:
                tempcrc16 = tempcrc16 << 1
            tempdata = tempdata << 1
        result = (result << 8) ^ tempcrc16
    return result


if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='encode')
    parser.add_argument('-d', '--directory', type=str, help='a directory with .bin files', default='./')
    parser.add_argument('-u', '--unit', type=int, help='Maximum transmission unit', default=150)
    parser.add_argument('-f', '--file', type=str, help='Encoded video file', default='in.avi')
    parser.add_argument('-m', '--msec', type=int, help='Millisecond of video', default=7000)

    arg = parser.parse_args()
    directory = arg.directory
    unit = arg.unit
    file = arg.file
    msec = arg.msec
    print(directory, unit, file, msec)

    # 各部分的位数，单位：字节
    ret_len = 1
    addr_len = 1
    len_len = 2
    crc_len = 4
    max_data_len = unit - ret_len - 2 * addr_len - len_len - crc_len

    head_ret = 0xCA  # 帧首定界符
    trans = 0xCC  # 转义字符
    src_addr = 0xAC  # 源地址
    dest_addr = 0xAC  # 目的地址

    head_ret_s = DecInt2BinStr(head_ret, ret_len)
    trans_s = DecInt2BinStr(trans, 1)
    src_addr_s = DecInt2BinStr(src_addr, addr_len)
    dest_addr_s = DecInt2BinStr(dest_addr, addr_len)

    data = BinFile2string(directory + 'source.bin')

    # 将data中需要转义的字符转义
    # 先转义转义字符
    index = data.find(trans_s)
    while index != -1:
        data = data[:index] + trans_s + data[index:]
        index = data.find(trans_s, index + 16)

    # 再转义帧首定界符
    index = data.find(head_ret_s)
    while index != -1:
        data = data[:index] + trans_s + data[index:]
        index = data.find(head_ret_s, index + 16)

    pattern = r'[01]{' + str(max_data_len * 8) + '}|[01]+'
    data_list = re.findall(pattern, data)  # 按最大长度max_data_len切分数据，最后长度不足的归为一组

    # test
    # print(BinStr2DecInt('11111110',1))
    # buf = [0x06, 0xaa, 0x02, 0x01, 0xab]
    # remainder = crc16(buf, len(buf))  # 余数
    # print(remainder)  # 2299528
    # remainder_s = DecInt2BinStr(remainder, crc_len)
    # print(remainder_s)
    # remainder_res = BinStr2DecInt(remainder_s, crc_len)
    # print(remainder_res)

    crc = calculate_crc(data_list)
    # print(len(crc) == len(data_list))  # True

    frames = []
    for i in range(len(data_list)):
        data_len = len(data_list[i])
        data_len_s = DecInt2BinStr(data_len, len_len)
        crc_s = DecInt2BinStr(crc[i], crc_len)

        frames.append(head_ret_s + dest_addr_s + src_addr_s + data_len_s + data_list[i] + crc_s)
        # 帧格式：帧首定界符+目的地址+源地址+长度+载荷+crc校验码

    num_of_frames = encode(frames, img_shape=(270, 480))
    img2video(file, fps=num_of_frames / (msec / 1000), num_of_frames=num_of_frames)
